package com.probiz.estore.core.search;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.hibernate.Criteria;
import org.springframework.util.Assert;

/**
 * 目标：统一的搜索条件。集成搜索条件（参数名和值）、分页、排序、搜索保存和重用等功能。
 * <P>
 * 使用流程：
 * <ol>
 * <li>在Controller用SearchCriteriaBuilder类提供的方法创建SC，对于ExportAll也应该在这层判断</li>
 * <li>Manager层只是负责在Controller和Dao层之间传输SearchCriteria，一般不需特别处理</li>
 * <li>Dao层根据SearchCriteria进行查询，获得查询结果列表，同时更新SC总记录数</li>
 * <li>JSP显示SearchCriteria对象，例如显示分页信息、总记录数等</li>
 * <li>支持多个Filter。所有搜索都基于Filter，缺省是default，但也可只使用一个Filter，这样相当于不使用Filter。Filter对应的HQL也可支持参数。</li>
 * </ol>
 * 其他说明:重构后，基于Filter，对应的HQL只能在Dao编写，不得再出现在Controller。
 * 
 * 
 */
public class SearchCriteria implements Serializable {

	private static final long	serialVersionUID			= -6898898403105146143L;


	private int					addParamIdx					= 0;



	/**
	 * 总页数
	 */
	private int					totalPageCount;														

	/**
	 * 当前页码
	 */
	private int					pageNo;														

	/**
	 * 每页的记录数，为0的时候表示不分页
	 */
	private int					pageSize;	
	
	/**
	 * 当前页第一条数据在List中的位置,从0开始
	 */
	private int					startIdx;														

	/**
	 * 总记录数
	 */
	private long				totalCount;													
	
	/**
	 * HQL
	 */
	private String				hql;

	/**
	 * 储存页面传入的参数，可以用来重现－编辑等，这样保存的SC与新的都通过统一方式操作，实际上存储的就是request.getParameterMap()
	 */
	private Map<String, Object>	paramMap					= null;
	/**
	 * 用于储存分析/转换过的值，目的是传给Hql用，代替？参数；除了储存Where子句的参数，也储存Order by子句的参数。
	 */
	private Object[]			paramValues					= null;

	/**
	 * 使用了此SC的URI
	 */
	private String				savedUri;

	/**
	 * 是否使用了上次的搜索条件
	 */
	private boolean				lastSearchCriteriaUsed = false;  

	
	/**
	 * 默认搜索条件是否已改变，提示已经有搜索项输入（提示高级搜索里面可能有搜索），增强用户体验。
	 * @since V3.5
	 */
	private boolean 			searchCriteriaChanged		= false;


	public final static SearchCriteria getHqlPagingInstance(String in_hql, Object[] in_paramValues, int in_pageNo, int in_pageSize,
			Map<String, Object> in_paramMap) {
		SearchCriteria sc = new SearchCriteria(in_hql, in_paramValues == null ? new Object[0] : in_paramValues, in_pageNo, in_pageSize, in_paramMap);
		return sc;
	}


	/**
	 * 缺省的构造方法。
	 * 
	 * @param in_pageNo    请求的页码
	 * @param in_pageSize  本页容量
	 */
	private SearchCriteria(String in_hql, Object[] in_paramValues, int in_pageNo, int in_pageSize, Map<String, Object> in_paramMap) {
		Assert.isTrue(in_pageNo > 0 && in_pageSize >= 0, "pageNo must >0 and pageSize must >= 0!");
		this.hql = in_hql;
		this.pageSize = in_pageSize;
		this.pageNo = in_pageNo;
		this.startIdx = (pageSize == 0) ? 0 : (in_pageNo - 1) * in_pageSize;
		this.paramValues = in_paramValues;
		this.paramMap = in_paramMap;
	}

	/**
	 * 增加查询参数，对应HQL的参数
	 * 注意：新的参数会添加到数组的最后。所以调用的顺序应该和hql里面参数的顺序相同。
	 * 大部分参数的值是根据request动态设置的，但是对于filter里面的参数，可以通过这方法设置。 *
	 * 
	 * @param paramValue
	 */
	public void addParamValue(Object paramValue) {
		if (addParamIdx >= 0) {
			paramValues = ArrayUtils.add(paramValues, addParamIdx, paramValue);
			addParamIdx++;
		}
	}
	/**
	 * 移除所有查询参数
	 */
	public void removeParamValues() {
		paramValues = ArrayUtils.EMPTY_OBJECT_ARRAY;
		addParamIdx = 0;
	}

	/**
	 * 修改页码和每页记录数
	 * 不改变搜索条件，因此可以重用
	 * 
	 */
	public void changePaging(int newPageNo, int newPageSize) {
		pageNo = newPageNo;
		pageSize = newPageSize;
		recalc();
	}
	
	/**
	 * 修改总记录数
	 * 一般是传SC给dao后由dao更新
	 * 
	 * @param in_totalCount
	 */
	public void setTotalCount(long in_totalCount) {
		this.totalCount = in_totalCount;
		recalc();
	}
	
	/**
	 * 重新计算分页值
	 * 
	 */
	private void recalc() {
		if(pageSize > 0){
			totalPageCount = (int) Math.ceil((double) totalCount / (double) pageSize);
			startIdx = (pageNo - 1) * pageSize;
		}else{
			totalPageCount = 1; 
			startIdx = 0;
		}
	}


	
	public int getTotalPageCount() {
		return totalPageCount;
	}
	

	public int getPageNo() {
		return pageNo;
	}

	public int getPageSize() {
		return pageSize;
	}
	
	public int getStartIdx() {
		return startIdx;
	}
	
	public long getTotalCount() {
		return this.totalCount;
	}

	/**
	 * 在创建了SearchCriteria后,还有可能按需设置hql. 但如自己编写hql查询,利用SearchCriteria来进行分页查询.
	 * 
	 * @param avalue
	 */
	public void setHql(String avalue) {
		this.hql = avalue;
	}
	
	public String getHql() {
		return this.hql;
	}

	public Map<String, Object> getParam() {
		return this.paramMap;
	}

	public Object[] getParamValues() {
		return this.paramValues;
	}

	public String getSavedUri() {
		return savedUri;
	}

	public void setSavedUri(String savedUri) {
		this.savedUri = savedUri;
	}


	public boolean getSearchCriteriaChanged() {
		return searchCriteriaChanged;
	}

	public void setSearchCriteriaChanged(boolean searchCriteriaChanged) {
		this.searchCriteriaChanged = searchCriteriaChanged;
	}


	public boolean isLastSearchCriteriaUsed() {
		return lastSearchCriteriaUsed;
	}

	public void setLastSearchCriteriaUsed(boolean lastSearchCriteriaUsed) {
		this.lastSearchCriteriaUsed = lastSearchCriteriaUsed;
	}

	@Override
	public String toString() {
		return "SearchCriteria [addParamIdx=" + addParamIdx + ", pageCount=" + totalPageCount + ", pageNo=" + pageNo + ", pageSize="
				+ pageSize + ", startIdx=" + startIdx + ", totalCount=" + totalCount + ", hql=" + hql  + ", paramMap=" + paramMap
				+ ", paramValues=" + Arrays.toString(paramValues) + ", lastSearchCriteriaUsed=" + lastSearchCriteriaUsed + ", savedUri=" + savedUri
				+ ", searchCriteriaChanged=" + searchCriteriaChanged + "]";
	}
	
	
	
}
